#include "types.h"
#include "config.h"
#include "riscv.h"
#include "mmap.h"
#include "functions.h"
#include "memory.h"
#include "console.h"
#include "sound_config.h"

static uintptr_t kernel_free_page_start;
uintptr_t kernel_free_page_end;
uintptr_t user_free_page_start;
uintptr_t mem_end;
static size_t next_free_page;
static size_t kernel_free_pages;
extern pte_t* root_page_table;
size_t free_page_index_base;
size_t free_page_nums;
size_t pages_for_Page_struct;
uintptr_t free_page_start;

struct Page *pages;
struct Page* pa2page(uintptr_t pa)
{
  return &pages[(pa >> RISCV_PGSHIFT) - free_page_index_base];
};

ppn_t page2ppn(struct Page *page) {
  return page - pages + free_page_index_base;
}

uintptr_t page2pa(struct Page *page) {
  if(page == NULL) return 0;
  return page2ppn(page) << RISCV_PGSHIFT;
}

uintptr_t page_alloc()
{
  uintptr_t addr = kernel_free_page_start + RISCV_PGSIZE * next_free_page;
  next_free_page++;
  memset((void*)addr, 0, RISCV_PGSIZE);
  return addr;
}

//为Page结构体预留的空间
uintptr_t page_alloc_init()
{
  uintptr_t addr = kernel_free_page_start + RISCV_PGSIZE * next_free_page;
  next_free_page += pages_for_Page_struct;
  memset((void*)addr, 0, RISCV_PGSIZE*pages_for_Page_struct);
  return addr;
}

uintptr_t vm_init()
{
  // HTIF address signedness and va2pa macro both cap memory size to 2 GiB
  mem_size = MIN(mem_size, 1U << 31);
  size_t mem_pages = mem_size >> RISCV_PGSHIFT;
  mem_end=DRAM_BASE+mem_size;

  kernel_free_pages = MAX(8, mem_pages >> (RISCV_PGLEVEL_BITS-1));

  extern char _end;
  kernel_free_page_start = ROUNDUP((uintptr_t)&_end, RISCV_PGSIZE);
  kernel_free_page_end = kernel_free_page_start + kernel_free_pages * RISCV_PGSIZE;
  user_free_page_start = kernel_free_page_end;

  // pages from kernel_free_page_start to mem_end
  free_page_nums = (ROUNDUP(mem_end, RISCV_PGSIZE) - kernel_free_page_start) >> RISCV_PGSHIFT;
  // pages for Page struct
  pages_for_Page_struct = free_page_nums * (sizeof(struct Page)) / RISCV_PGSIZE;

  //init memory for Page struct
  pages = (struct Page *)(page_alloc_init());

  // free pages start after Page struct
  free_page_start = kernel_free_page_start + pages_for_Page_struct * RISCV_PGSIZE;
  free_page_index_base = free_page_start >> RISCV_PGSHIFT;

  //pmm_manager init
  pmm_manager.init_physical_memory();
  pmm_manager.init_memmap(free_page_start, free_page_nums);
  // pmm_manager.pmm_check();

  // alloc page for root_page_table
  root_page_table = (void*)pmm_manager.alloc_pages(1);
  if(DEBUG_MEM){
    printk("root page table : %p \n",root_page_table);
  }
  // virtual addresses map directly to physical addresses from DRAM_BASE to mem_end
  map_kernel_range(DRAM_BASE, DRAM_BASE, mem_end - DRAM_BASE, PROT_READ|PROT_WRITE|PROT_EXEC);
  map_kernel_range(SOUND_CARD_MEM_BASE, SOUND_CARD_MEM_BASE, SOUND_CARD_MEM_LEN, PROT_READ|PROT_WRITE|PROT_EXEC);

  if(DEBUG_TIMER){
    map_kernel_range(0x200b000, 0x200b000, PGSHIFT, PROT_READ|PROT_WRITE|PROT_EXEC);
    map_kernel_range(0x2004000, 0x2004000, PGSHIFT, PROT_READ|PROT_WRITE|PROT_EXEC);
  }

  //open paging
  w_satp(MAKE_SATP(root_page_table));
  //flush tlb
  flush_tlb();

  uintptr_t kernel_stack_top = pmm_manager.alloc_pages(1) + RISCV_PGSIZE;
  return kernel_stack_top;
}

free_area_t free_area;

#define free_list (free_area.free_list)
#define nr_free (free_area.nr_free)


uintptr_t alloc_pages(size_t n){
  // if(n !=1)
  //     panic("alloc page num > 1");
  // uintptr_t physical_adderss= page_alloc();
  // return physical_adderss;

  if (n > nr_free) {
      return 0;
  }
  struct Page *page = NULL;
  list_entry_t *le = &free_list;
  while ((le = list_next(le)) != &free_list) {
      struct Page *p = le2page(le, page_link);
      if (p->property >= n) {
          page = p;
          break;
      }
  }
  if (page != NULL) {
      list_entry_t* prev = list_prev(&(page->page_link));
      list_del(&(page->page_link));
      if (page->property > n) {
          struct Page *p = page + n;
          p->property = page->property - n;
          list_add(prev, &(p->page_link));
      }
      nr_free -= n;
  }
  memset((char *)page2pa(page),0,PGSIZE*n);

  if(DEBUG_MEM){
    printk("alloc:alloc mem %p n_free %d \n",page2pa(page),nr_free);
    printMem();
    printk("\n");
  }

  return page2pa(page);
}


void init_physical_memory(){
  if(DEBUG_MEM){
    printk("init_physical_memory\n");
  }
  list_init(&free_list);
  nr_free = 0;
}


void init_memmap(uintptr_t base, size_t n){
  struct Page *pagebase = pa2page(base);
  struct Page *p = pagebase;
  for (; p != pagebase + n; p ++) {
    p->flags = p->property = 0;
    p->ref = 0;
    //set_page_ref(p, 0);
  }
  pagebase->property = n;
  nr_free += n;
  if (list_empty(&free_list)) {
      list_add(&free_list, &(pagebase->page_link));
  } else {
      list_entry_t* le = &free_list;
      while ((le = list_next(le)) != &free_list) {
          struct Page* page = le2page(le, page_link);
          if (pagebase < page) {
              list_add_before(le, &(pagebase->page_link));
              break;
          } else if (list_next(le) == &free_list) {
              list_add(le, &(pagebase->page_link));
          }
      }
  }
}

void free_pages(uintptr_t base, size_t n){



  if(DEBUG_MEM){
    printk("free mem %p\n",base);
    printMem();

  }
  struct Page* pagebase = pa2page(base);

  struct Page *p = pagebase;

  for (; p != pagebase + n; p ++) {
      uint64 p_pa=(uint64)page2pa(p);
      if(pa_in_freelist(p_pa))
      {  panic("pa in pages %p \n",p_pa);}
      p->flags = 0;
      p->ref = 0;
      //set_page_ref(p, 0);
  }


  pagebase->property = n;
  nr_free += n;

  if (list_empty(&free_list)) {
      if(DEBUG_MEM)
       {   printk("list empty\n");}
      list_add(&free_list, &(pagebase->page_link));
  } else {
      list_entry_t* le = &free_list;
      while ((le = list_next(le)) != &free_list) {
          struct Page* page = le2page(le, page_link);

          if (pagebase < page) {
              list_add_before(le, &(pagebase->page_link));
              if(DEBUG_MEM)
                {printk("addbefore\n");}
              break;
          } else if (list_next(le) == &free_list) {
            if(DEBUG_MEM)
            {      printk("add list\n"); }
              list_add(le, &(pagebase->page_link));
          }
      }
  }



  list_entry_t* le = list_prev(&(pagebase->page_link));
  if (le != &free_list) {
      p = le2page(le, page_link);
      if (p + p->property == pagebase) {
          p->property += pagebase->property;
          list_del(&(pagebase->page_link));
          pagebase = p;
      }
  }

  le = list_next(&(pagebase->page_link));
    if (le != &free_list) {
        p = le2page(le, page_link);
        if (pagebase + pagebase->property == p) {
            pagebase->property += p->property;
            list_del(&(p->page_link));
        }
    }

  if(DEBUG_MEM){
    printMem();
    printk("\n");
  }
}

size_t nr_free_pages(){
    return nr_free;
}

bool pa_in_freelist(uint64 pa){
  list_entry_t *le=&free_list;
  while ((le=list_next(le))!=&free_list){
      struct Page *p=le2page(le,page_link);
      if(pa>=page2pa(p)&&pa<(page2pa(p)+p->property*PGSIZE)){
        return 1;
      }
  }
  return 0;
}
void basic_check() {
  printk("basic check!\n");
  struct Page *p0, *p1, *p2;
  p0 = p1 = p2 = NULL;

  // kassert((p0 = pa2page(alloc_page())) != NULL);
  // kassert((p1 = pa2page(alloc_page())) != NULL);
  // kassert((p2 = pa2page(alloc_page())) != NULL);
  uintptr_t pa0, pa1, pa2;
  pa0 = pa1 = pa2 = 0;
  kassert((pa0 = alloc_page())!= 0);
  p0 = pa2page(pa0);
  kassert((pa1 = alloc_page())!= 0);
  p1 = pa2page(pa1);
  kassert((pa2 = alloc_page())!= 0);
  p2 = pa2page(pa2);


  kassert(p0 != p1 && p0 != p2 && p1 != p2);
  kassert(p0->ref == 0 && p1->ref == 0 && p2->ref == 0);


  list_entry_t free_list_store = free_list;
  list_init(&free_list);
  kassert(list_empty(&free_list));

  unsigned int nr_free_store = nr_free;
  nr_free = 0;
  free_page(pa0);
  free_page(pa1);
  free_page(pa2);
  kassert(nr_free == 3);


  // kassert((p0 = pa2page(alloc_page())) != NULL);
  // kassert((p1 = pa2page(alloc_page())) != NULL);
  // kassert((p2 = pa2page(alloc_page())) != NULL);

  pa0 = pa1 = pa2 = 0;
  kassert((pa0 = alloc_page())!= 0);
  p0 = pa2page(pa0);
  kassert((pa1 = alloc_page())!= 0);
  p1 = pa2page(pa1);
  kassert((pa2 = alloc_page())!= 0);
  p2 = pa2page(pa2);

  kassert(alloc_page() == 0);

  free_page(pa0);
  kassert(!list_empty(&free_list));

  //struct Page *p;
  //kassert((p = alloc_page()) == p0);

  uintptr_t pa= 0;
  kassert((pa = alloc_page()) == pa0);
  //kassert(alloc_page() == NULL);
  kassert(alloc_page() == 0);

  kassert(nr_free == 0);
  free_list = free_list_store;
  nr_free = nr_free_store;

  free_page(pa);
  free_page(pa1);
  free_page(pa2);
}

// struct physical_memory_manager {
//     const char *pmm_manager_name;
//     void (*init_physical_memory)(void);
//     void (*init_memmap)(uintptr_t *base,size_t n);
//     struct uintptr_t *(*alloc_pages)( size_t n);
//     void (*free_pages)( uintptr_t *base, size_t n);
//     size_t (*nr_free_pages)(void);  // return the number of free pages
//     void (*pmm_check)(void);
// };

const struct physical_memory_manager  pmm_manager = {
    .pmm_manager_name = "default_pmm_manager",
    .init_physical_memory = init_physical_memory,
    .init_memmap = init_memmap,
    .alloc_pages = alloc_pages,
    .free_pages = free_pages,
    .nr_free_pages = nr_free_pages,
    .pmm_check = basic_check,
};


// create an empty user page table.
// returns 0 if out of memory.
pagetable_t
uvmcreate()
{
  pagetable_t pagetable;
  pagetable = (pagetable_t) pmm_manager.alloc_pages(1);
  if(pagetable == 0)
    return 0;
  memset(pagetable, 0, PGSIZE);
  return pagetable;
}


void printMem(){

  list_entry_t *le=&free_list;
  while ((le=list_next(le))!=&free_list){
    struct Page *p=le2page(le,page_link);
      printk("mem start %p  pages num %d startpage %p \n",page2pa(p),p->property,p);
  }
    printk("------------------\n");
}